/*
 * @ObjectFactory.java	2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;


import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * ObjectFactory is the accessor for getting all instances of Queue, QueueDumper and Register. Because only one instance of each must exists
 * @author Leonardo Celati
 */
public final class LogFactory implements Constant{
	
	/**
	 * Represents status
	 * @author leonardo
	 */
	public enum Status {
		INIT,
		RUNNING,
		SHUTDOWN,
	}
	/**
	 * Status of LogFactory
	 */
	private static Status status;
    
	/**
     * The Queue holder
     */
    private static Queue<? extends Entry> q;
    /**
     * The Register holder
     */
    private static Register<? extends Dumper> r;
    /**
     * The QueueDumper holder
     */
    private static QueueDumper qd;
    /**
     * Log holder
     */
    private static Log l;
    /**
     * Log holder
     */
    private static ArrayList<Dumper> dumpers;
	/**
	 * @return the status
	 */
	public static Status getStatus() {
		return status;
	}


	/**
     * Main accessor for log
     * @return valid instance of Log
     */
    public static Log getLog() {
    	
        // simple if...we didn't build yet a Log
        // so it is constructed and init() called
        if (l == null) {
            init();
        }
        return l;
    }
        
    
    /**
     * Private method for init our Log
     */
    private static void init() {

        try {
        	
            // load property file
            Properties fp = propertyLoader();
            
            // init log...
            initLog(fp);
            // get dumpers
            initDumper(fp);
           
            // time to build other objects, first we retrieve a set
            // of property and pass to each method
            Properties pr = new Properties();
            Properties pq = new Properties();
            Properties pqd = new Properties();

            // iterate trough our set...
            keybrowser:
            for (Object o: fp.keySet()) {
            	// the key to find in this property
                String key = o.toString();
                // see if we can find 'register' in set
                if (key.indexOf(Constant.PROP_ROOT + "register.") > -1) {
                    // if yes...we add to our properties
                    pr.put(key.replaceAll(Constant.PROP_ROOT + "register.", ""), (String) fp.get(key));
                    continue keybrowser;
                }
                // see if we can find 'queue' in set
                if (key.indexOf(Constant.PROP_ROOT + "queue.") > -1) {
                    // if yes...we add to our properties
                    pq.put(key.replaceAll(Constant.PROP_ROOT + "queue.", ""), (String) fp.get(key));
                    continue keybrowser;
                }
                // see if we can find 'queuedumper' in set
                if (key.indexOf(Constant.PROP_ROOT + "queuedumper.") > -1) {
                    // if yes...we add to our properties
                    pqd.put(key.replaceAll(Constant.PROP_ROOT + "queuedumper.", ""), (String) fp.get(key));
                    continue keybrowser;
                }
            }

            // Init our Register and store in dumper...
            initRegister(pr, dumpers);

            // and now...the Queue itself, with if present a desired register classname
            initQueue(pq);

            // we check if our Queue implements NotCumulable
            // which means we drop our entry directly to Dumper
            // and we do not need to start QueueDumper....
            if (q.isCumulable()) {
                Thread t = new Thread(initQueueDumper(pqd));
                t.setName("QueueDumper");
                t.start();
            }

        } catch (LogInitializationException e) {
            // if we get here we couldn't intialize log...
        	// so we take the default one casting to Log...
        	l = new Log();
        	
        } catch (DumperInitializationException e) {
            // if we get here we couldn't intialize the dumper
        	// properly...
        	l = new Log();
        	
        } catch (RegisterInitializationException e) {
        	// the safe log first...
        	l = new Log();
        	// then we have some dumpers left...
        	for (Dumper d: dumpers)
        		d.kill();
        	
        } catch (QueueInitializationException e) {
        	// the safe log first...
        	l = new Log();
            // if we get here we couldn't intialize the queue...
        	// the register first...
        	r.shutdown();	
        } catch (QueueDumperInitializationException e) {
        	// the safe log first...
        	l = new Log();
            // if we get here we couldn't intialize the queue...
        	// the register first...
        	r.shutdown();	
        }
    }

	/**
	 * Initialize all our dumper
	 * @param fp
	 * @throws DumperInitializationException
	 */
	private static void initDumper(Properties fp) throws DumperInitializationException {
		
		try {
	        // start with looking for dumper.name=.....,....,.....
	        String[] declaredDumpers = Pattern.compile(",").split(fp.getProperty("dumper.name"));
	        // nothing to say...if dumper are found
	        // we store them into register
	        // if any error occur initializing the dumper we run in emergency mode
	        if (declaredDumpers.length > 0) {
	            // uhm...some dumper found, let's have a look
	            // we need to store them on register
	            dumpers =  new ArrayList<Dumper>(declaredDumpers.length);
	            // looping trough them...
	            dumperloop:
	            for (String name : declaredDumpers) {
	            	// dumper holder...
	            	Dumper dumper = null;
	                try {
	                    // load class and its corresponding constructor...
	                    Class<?> c = Class.forName(fp.getProperty(PROP_ROOT + name + ".classname"));
	                    
	                    // building our set of properties to pass to Dumper constructor
	                    Properties dp = new Properties();
	                    // starting with the name
	                    dp.setProperty("name", name);
	                    
	                    // iterate trough our set...
	                    for (Object o: fp.keySet()) {
	                        // the key to find in this property
	                        String key = (String)o;
	                        // all the property for this dumper has to start with this root
	                        // example if the name of the dumper is xyz then we will look for
	                        // dumper.xyz.[prop]
	                        if (key.indexOf(Constant.PROP_ROOT + name + ".") > -1) {
	                            // if yes...we add to our properties
	                            dp.put(key, fp.get(key));
	                        }
	                    }
	
	                    // use reflection for the rest...
	                    Constructor<?> cst = c.getConstructor(Properties.class);
	                    dumper = (Dumper) cst.newInstance(new Object[]{dp});
	                    // store dumpers in array
	                    dumpers.add(dumper);
	                	                
	                } catch (ClassNotFoundException e) {
	                    throw new DumperInitializationException("Class dumper has not found");
	                } catch (InvocationTargetException e) {                    	
	                    throw new DumperInitializationException(e.getCause().getMessage());
	                } finally {
	                	// kill all dumper if not null...
	                	if (dumpers !=null ) {
	                		// browsing...and killing...
		                	for (Dumper x: dumpers) {
		                		if (x != null)
		                			x.kill();
		                	}
	                	}
	                }
	            }
	        }
            
        } catch(Exception e) {
        	throw new DumperInitializationException(e);
        }
		
	}
    
    
    /**
     * Load the dump.properties file in the classpath then if not found, default dump_default.properties is taken from the package
     * @return Properties
     */
    private static Properties propertyLoader() {
        // Holder for dump properties file
        InputStream is = null;
        Properties p = new Properties();
        try {
            // try to see if user has its own properties file
            is = Log.class.getClassLoader().getResourceAsStream(PROP_NAME);
            // then if null, a default one must exist in the package
            if (is == null) {
                is = Log.class.getClassLoader().getResourceAsStream(PROP_DEFAULT);
            // doing some basic transformation
            }
            p.load(is);
            return p;

        } catch (IOException e) {
            return null;
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                // we are doing nothing
            }
        }
    }    
    
    /**
     * Initialize our log
     * @param p custom set of properties
     * @throws LogInitializationException
     */
    static void initLog(Properties p) throws LogInitializationException {
        // we first check if we already have a
        // log available, if not we instantiate
        // and store it in variable 'l'
        if (l==null) {
            try {
                // get default if className is null
                // find the default in Constant interface
            	String className;
                if (p.containsKey("classname")) {
                	className = (String)p.get("classname");
                } else {
                    className = DEFAULT_LOG;
                }
                
                // deciding which instance of Queue we want to create
                Class<?> c = Class.forName(className);
                Constructor<?> cst = c.getConstructor(Properties.class);
                l = (Log) cst.newInstance(new Object[] {p});

            } catch (RuntimeException e) {
                throw new LogInitializationException(e);
            } catch (Exception e) {
            	throw new LogInitializationException(e);
            }
        }    	
    }
    
    
    /**
     * This method will return an instance only of Queue depending
     * on which implementation we required
     * @param className example it.wzm.common.lightweight.object.FastQueue
     * @return an instance of Queue
     */
    @SuppressWarnings("unchecked")
    private static void initQueue(Properties p) throws QueueInitializationException {
    	
        // we first check if we already have a
        // queue available, if not we instantiate
        // and store it in variable 'q'
        if (q==null) {
            try {
                // get default if className is null
                // find the default in Constant interface
            	String className;
                if (p.containsKey("classname")) {
                	className = (String)p.get("classname");
                } else {
                    className = DEFAULT_QUEUE;
                }

                // deciding which instance of Queue we want to create
                Class<?> c = Class.forName(className);
                Constructor<?> cst = c.getConstructor(Properties.class);
                q = (Queue<? extends Entry>) cst.newInstance(new Object[] {p});

            } catch (RuntimeException e) {
            	throw new QueueInitializationException(e);
            } catch (Exception e) {
            	throw new QueueInitializationException(e);
            }         
        }
    }
    
    
    /**
     * Initialize our Register with a given implementation classname and a set of Dumper
     * @param className the name of the class
     * @param d Our array of Dumper
     * @return A valid Register
     */
    @SuppressWarnings("unchecked")
    static void initRegister(Properties p, ArrayList<? extends Dumper> d) throws RegisterInitializationException {

        if (r == null)  {
            // if classname is null we try with the default implementation
        	String className;
            if (p.containsKey("classname")) {
            	className = (String)p.get("classname");
            } else {
                className = DEFAULT_REGISTER;
            }
            // then...all that follows to get a valid Register
            try {
                // deciding which instance of Register we want to create
                Class<?> c = Class.forName(className);
                Constructor<?> cst = c.getConstructor(Properties.class,ArrayList.class);
                r = (Register<? extends Dumper>) cst.newInstance(new Object[] {p,d});
                
            } catch (RuntimeException e) {
            	throw new RegisterInitializationException(e);
            } catch (Exception e) {
                throw new RegisterInitializationException(e);
            }
        }
    }

    /**
     * Initialize our QueueDumper
     * @return A valid QueueDumper
     */
    static QueueDumper initQueueDumper(Properties p) throws QueueDumperInitializationException {
    	
        if (qd == null) {
            qd = new QueueDumper(p);
        }
        return qd;
    }
    
    /**
     * Getter for Queue
     * @return Queue
     */
    static Queue getQueue() {
        return q;
    }
    
    /**
     * Getter for Register
     * @return Register
     */
    static Register<? extends Dumper> getRegister() {
        return r;
    }
    
    /**
     * Getter for QueueDumper
     * @return QueueDumper
     */
    static QueueDumper getQueueDumper() {
        return qd;
    }
    
    /**
     * Free resources...
     */
    public static void shutdown() {

    	// shutdown QueueDumper
    	qd.shutdown();
    	// before shutdown we try to clear any entry we have in log.
    	LinkedList<? extends Entry> ll = q.retainAll();
    	r.dispatchToDumper(ll);
    	// now we close register
    	r.shutdown();
    	r = null;
    	// and Queue...
    	q = null;
    	// and log...
    	l = null;
    }
    
}

